// 没有assign方法添加
if (typeof Object.assign != 'function') {
  (function () {
    Object.assign = function (target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      let output = Object(target);
      for (let index = 1; index < arguments.length; index++) {
        let source = arguments[index];
        if (source !== undefined && source !== null) {
          for (let nextKey in source) {
            if (source.hasOwnProperty(nextKey)) {
              output[nextKey] = source[nextKey];
            }
          }
        }
      }
      return output;
    };
  })();
}

/**
 * 判断是否为js dom对象
 * 由于在Chrome,Opera中HTMLElement的类型为function,因此需要做浏览器兼容性判断
 * @param obj 目标对象
 * @returns {boolean} 为js dom对象返回true
 */
function isDom(obj) {
  return (typeof HTMLElement === 'object') ? obj instanceof HTMLElement : obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
}

/**
 *
 * @param ele 元素dom节点
 * @param $ css对象或字符串
 * @param _ 样式值
 * @returns {*}
 */
function eleCss(ele, $, _) {
  if (!ele || !isDom(ele)) {
    return;
  }
  if (typeof $ === 'string') {
    if (_ === undefined) {
      return window.getComputedStyle(ele)[$];
    }
    ele.style[$] = _;
  } else if (typeof $ === 'object') {
    let keys = Object.keys($);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      ele.style[key] = $[key];
    }
  }
  return ele;
}

function eleCssSize(ele, $) {
  return parseInt(eleCss(ele, $)) || 0;
}

function eleOffset(ele) {
  let totalLeft = null, totalTop = null, par = ele.offsetParent;
  //首先把自己本身的进行累加
  totalLeft += ele.offsetLeft;
  totalTop += ele.offsetTop;

  //只要没有找到body，我们就把父级参照物的边框和偏移量累加
  while (par) {
    if (navigator.userAgent.indexOf("MSIE 8.0") === -1) {
      //不是标准的ie8浏览器，才进行边框累加
      //累加父级参照物边框
      totalLeft += par.clientLeft;
      totalTop += par.clientTop;
    }
    //累加父级参照物本身的偏移
    totalLeft += par.offsetLeft;
    totalTop += par.offsetTop;
    par = par.offsetParent;
  }
  return {left: totalLeft, top: totalTop};
}


let currentDom;
let currentTooltip;
let timeout;
// 生成锁，确保任意元素以及方法仅仅只执行一次。避免多绑定以及多销毁
let lock = false;
// 获取事件监听，用于重置绑定
let listenerMap = {
  enter: new Map(), leave: new Map()
}

/**
 * <p>使用方式 new Tips(options);</p>
 * <p>支持的options配置项：</p>
 *     <ol>
 *         <li>id: 目标</li>
 *         <li>width: 气泡框宽度</li>
 *         <li>height: 气泡框高度</li>
 *         <li>minWidth: 气泡框最小宽度</li>
 *         <li>minHeight: 气泡框最小高度</li>
 *         <li>maxWidth: 气泡框最大宽度，默认为dom宽度的两倍</li>
 *         <li>maxHeight: 气泡框最大高度</li>
 *         <li>color: 字体颜色，默认#000</li>
 *         <li>backgroundColor: 背景颜色，默认#fff</li>
 *         <li>position: 气泡框位置，可选值有auto|top|right|bottom|left，为auto时自动判断</li>
 *         <li>horizontalAlign: 水平对齐，可选值有center|left|right，仅在position为top和bottom有效</li>
 *         <li>verticalAlign: 垂直对齐，可选值有middle|top|bottom，仅在position为left和right有效</li>
 *         <li>content: 气泡框内容，string|function类型</li>
 *         <li>duration: 气泡框显示时长，单位ms，默认100s</li>
 *         <li>borderWidth: 气泡框边框大小，为0无边框，默认为0</li>
 *         <li>borderColor: 气泡框边框颜色，默认值#ccc,</li>
 *         <li>borderRadius: 气泡框圆角，默认5px</li>
 *     </ol>
 */
export default function Tips(options) {
  let targetObj;
  let id = options.id;
  if (isDom(id)) {
    targetObj = [id];
  } else if (targetObj instanceof HTMLCollection || targetObj instanceof NodeList) {
    targetObj = id;
  } else if (id && typeof id === 'string') {
    targetObj = document.querySelectorAll(id);
  } else {
    throw new TypeError('The parameter id is required, and is either a string or javaScript dom object.');
  }
  if (!targetObj || targetObj.length === 0) {
    return;
  }

  let params = Object.assign({
    position: 'auto',
    horizontalAlign: 'center',
    verticalAlign: 'middle',
    content: "",
    duration: 100,
    borderWidth: 0,
    borderRadius: 5
  }, options);
  // 获取 targetObj 是否有绑定过元素，如果有，从 map 中获取并取消监听
  let enterFn, leaveFn;
  if ((enterFn = listenerMap.enter.get(targetObj[0])) && (leaveFn = listenerMap.leave.get(targetObj[0]))) {
    targetObj[0].removeEventListener('mouseenter', enterFn);
    targetObj[0].removeEventListener('mouseleave', leaveFn);
  }
  /** 定义 element 的鼠标移入事件 */
  let mouseenter = function () {
    // 如果锁为 false 时则代表通行
    if (!lock) {
      // 获取锁
      lock = true;
      let that = this;
      window.clearTimeout(timeout);
      if (that === currentDom) {
        return;
      }
      let tooltips = document.getElementsByClassName('tooltip');
      for (let i = 0; i < tooltips.length; i++) {
        tooltips.item(i).remove();
      }
      // 生成 tooltip 元素
      let tooltip = document.createElement('div');
      tooltip.classList.add('tooltip');
      document.body.appendChild(tooltip);

      // 生成自定义的元素
      let content = params.content;
      if (typeof params.content === 'function') {
        content = params.content(params, that);
      }
      let tip = document.createElement('div');
      tip.classList.add('tips');
      tip.innerHTML = content;
      // 自定义追加绑定事件
      if (params.click && typeof params.click === 'function') {
        // 追加绑定事件
        tip.onclick = function (ev) {
          let data = {
            event: ev
          }
          if (params.data) {
            // 自定义携参
            data.data = params.data;
          }
          params.click(data);
        }
      }

      tooltip.appendChild(tip);
      let tipsTail = document.createElement('div');
      tipsTail.classList.add('tips-tail');
      tooltip.appendChild(tipsTail);

      let tipsTail2 = document.createElement('div');
      tipsTail2.classList.add('tips-tail2');
      tooltip.appendChild(tipsTail2);

      currentTooltip = tooltip;
      currentDom = that;

      let color = params.color || '#000';
      let backgroundColor = params.backgroundColor || '#fff';
      let borderWidth = isNaN(params.borderWidth) ? 0 : Number(params.borderWidth);
      let borderColor = params.borderColor || '#ccc';

      eleCss(tooltip, {
        width: params.width,
        height: params.height,
        minWidth: params.minWidth,
        minHeight: params.minHeight,
        maxWidth: params.maxWidth || (2 * that.offsetWidth + 'px'),
        maxHeight: params.maxHeight,
        borderRadius: params.borderRadius || '5px',
        position: 'absolute',
        overflow: 'visible',
        padding: borderWidth + 'px',
        backgroundColor: borderColor,
        zIndex: '99999'
      });

      eleCss(tip, {
        color: color, backgroundColor: backgroundColor, // padding: '5px',
        borderRadius: params.borderRadius || '5px'
      });

      let gap = 5;
      let tipsCssObj = {
        position: 'absolute', width: '0px', height: '0px', border: (borderWidth + gap + 1) + 'px solid transparent'
      };

      eleCss(tipsTail, tipsCssObj);
      eleCss(tipsTail2, tipsCssObj);

      let position;
      let top;
      let left;

      let offset = eleOffset(that);
      let thatOuterWidth = that.offsetWidth;
      let thatOuterHeight = that.offsetHeight;

      let tooltipOuterWidth = tooltip.offsetWidth;
      let tooltipOuterHeight = tooltip.offsetHeight;

      // 自动计算气泡框弹出位置
      // 按右 - 上 - 左 - 下的顺序计算
      if (params.position === 'auto' || Array.isArray(params.position)) {
        let positionAutoOrderArr = params.position === 'auto' || params.position.length === 0 ? ['right', 'top', 'left', 'bottom'] : params.position;
        let windowWidth = document.body.clientWidth;
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

        for (let i = 0; i < positionAutoOrderArr.length; i++) {
          let currentPosition = positionAutoOrderArr[i];
          if (currentPosition === 'right' && offset.left + thatOuterWidth + tooltipOuterWidth + gap < windowWidth) {
            position = 'right';
            // 到顶部的距离：offset.top - (document.documentElement.scrollTop || document.body.scrollTop)
          } else if (currentPosition === 'top' && offset.top - scrollTop - tooltipOuterHeight - gap > 0 && offset.left + tooltipOuterWidth < windowWidth) {
            position = 'top';
          } else if (currentPosition === 'left' && offset.left - tooltipOuterWidth - gap > 0) {
            position = 'left';
          } else if (currentPosition === 'bottom') {
            position = 'bottom';
          }

          // 找到合适的定位退出for循环
          if (position) {
            break;
          }
        }
        // 没有找到合适的定位使用给定定位的第一种定位方式
        if (!position) {
          position = positionAutoOrderArr[0];
        }
      } else {
        position = params.position;
      }

      switch (position) {
        case 'top':
          top = offset.top - tooltipOuterHeight - gap - borderWidth;
          left = offset.left;
          tipsCssObj = {
            top: '100%', left: '50%', borderTopColor: borderColor, transform: 'translate(-50%, 0)'
          };
          break;
        case 'bottom':
          top = offset.top + thatOuterHeight + gap + borderWidth;
          left = offset.left;
          tipsCssObj = {
            top: '0%', left: '50%', borderBottomColor: borderColor, transform: 'translate(-50%, -100%)'
          };
          break;
        case 'left':
          top = offset.top;
          left = offset.left - tooltipOuterWidth - gap - borderWidth;
          tipsCssObj = {
            top: '50%', left: '100%', borderLeftColor: borderColor, transform: 'translate(0, -50%)'
          };
          break;
              // 其余都往右
        default:
          position = 'right';
          top = offset.top;
          left = offset.left + thatOuterWidth + gap + borderWidth;
          tipsCssObj = {
            top: '50%', left: 0, borderRightColor: borderColor, transform: 'translate(-100%, -50%)'
          };
      }


      if (position === 'top' || position === 'bottom') {
        if (params.horizontalAlign === 'left' || params.horizontalAlign === 'right') {
          let tipsLeft = thatOuterWidth / 2;
          if (params.horizontalAlign === 'right') {
            let leftOffset = thatOuterWidth - tooltipOuterWidth;
            left += leftOffset;
            tipsLeft -= leftOffset;
          }
          // 当气泡框宽度大于目标dom宽度时，取目标dom宽度的一半作为气泡脚水平位置
          if (tooltipOuterWidth > thatOuterWidth) {
            tipsCssObj['left'] = tipsLeft + 'px';
          }
        } else {
          // 气泡框水平居中
          left += (thatOuterWidth - tooltipOuterWidth) / 2;
        }
      } else {
        if (params.verticalAlign === 'top' || params.verticalAlign === 'bottom') {
          let tipsTop = thatOuterHeight / 2;
          if (params.verticalAlign === 'bottom') {
            let topOffset = thatOuterHeight - tooltipOuterHeight;
            top += topOffset;
            tipsTop -= topOffset;
          }

          // 当气泡框宽度大于目标dom宽度时，取目标dom高度的一半作为气泡脚垂直位置
          if (tooltipOuterHeight > thatOuterHeight) {
            tipsCssObj['top'] = tipsTop + 'px';
          }
        } else {
          top += (thatOuterHeight - tooltipOuterHeight) / 2;
        }
      }

      eleCss(tooltip, {
        top: top + 'px', left: left + 'px'
      });
      eleCss(tipsTail, tipsCssObj);
      tipsCssObj['border' + position.charAt(0).toUpperCase() + position.substring(1) + 'Color'] = backgroundColor;
      eleCss(tipsTail2, tipsCssObj);

      if (borderWidth > 0) {
        eleCss(tipsTail2, 'borderWidth', eleCssSize(tipsTail2, 'borderWidth') - 1 + 'px');

        let tipsTailTop = eleCssSize(tipsTail, 'top');
        let tipsTailLeft = eleCssSize(tipsTail, 'left');
        let tipsTail2Top = eleCssSize(tipsTail2, 'top');
        let tipsTail2Left = eleCssSize(tipsTail2, 'left');

        switch (position) {
          case 'top':
            eleCss(tipsTail2, 'top', tipsTail2Top - borderWidth - 1 + 'px');
            break;
          case 'bottom':
            eleCss(tipsTail, 'top', tipsTailTop + 1 + 'px');
            eleCss(tipsTail2, 'top', tipsTail2Top + borderWidth + 1 + 'px');
            break;
          case 'left':
            eleCss(tipsTail, 'left', tipsTailLeft - 1 + 'px');
            eleCss(tipsTail2, 'left', tipsTail2Left - borderWidth - 1 + 'px');
            break;
          default:
            eleCss(tipsTail, 'left', tipsTailLeft + 1 + 'px');
            eleCss(tipsTail2, 'left', tipsTail2Left + borderWidth + 1 + 'px');
        }
      }

      // 当气泡框超出显示屏时
      if (tooltipOuterHeight !== tooltip.offsetHeight) {
        if (['top', 'left', 'right'].indexOf(position) !== -1) {
          let tooltipHeightChangeGap = tooltip.offsetHeight - tooltipOuterHeight;
          let tooltipOffsetTop = eleOffset(tooltip).top;

          let offsetTop;
          if (position === 'top') {
            offsetTop = tooltipOffsetTop - tooltipHeightChangeGap;
          } else {
            offsetTop = tooltipOffsetTop - tooltipHeightChangeGap / 2;
          }
          eleCss(tooltip, {top: offsetTop + 'px'});
        }
      }


      // 绑定
      tooltip.addEventListener('mouseenter', function () {
        window.clearTimeout(timeout);
      });
      tooltip.addEventListener('mouseleave', function () {
        timeout = window.setTimeout(function () {
          if (that === currentDom) {
            currentTooltip.remove();
            currentDom = undefined;
          }
        }, params.duration);
      });

    }
  }

  /** 定义 element 的鼠标移出事件 */
  let mouseleave = function () {
    let that = this;
    // element 锁被锁上时，生成 timeout 后释放锁
    if (lock) {
      timeout = window.setTimeout(function () {
        if (that === currentDom) {
          currentTooltip.remove();
          currentDom = undefined;
        }
      }, options.duration);
      lock = false;
    }
  }
  for (let i = 0; i < targetObj.length; i++) {
    let element = targetObj[i];
    element.addEventListener('mouseenter', mouseenter);
    element.addEventListener('mouseleave', mouseleave);
    listenerMap.enter.set(element, mouseenter);
    listenerMap.leave.set(element, mouseleave);
  }
}
